<template>
  <div>
    <h1>跨域资源共享（CORS）</h1>
    <section>
      <h2>什么是跨域资源共享</h2>
      <p>实际上应该叫“跨源资源共享”才对，英语叫Cross Origin Resource Sharing，简称CORS。但是国内都叫跨域，那就这么叫吧。</p>
      <p>跨域资源共享实际是不同的源之间的信任规约。</p>
      <p>
        由于浏览器的同源政策，使得JS在通常情况下无法向不同源的服务器发送AJAX请求。
        随着微服务的普及，开发者迫切需要一种能够在不同的子域名之间共享资源的方式，
        于是CORS就应运而生。
      </p>
    </section>
    <section>
      <h2>跨域资源共享的流程</h2>
      <v-carousel :cycle="false" hide-controls>
        <v-carousel-item>
          <h3>预检请求（preflight request）</h3>
          <p>
            <pre>
              OPTIONS http://api.example.com/foo/bar HTTP/1.1
              Origin: http://www.example.com
              Access-Control-Request-Method: POST
              Access-Control-Request-Headers: Content-Type, Authorization
            </pre>
          </p>
          <hr/>
          <ul>
            <li>预检请求的method是OPTIONS</li>
            <li>预检请求的URL和正式请求的URL相同</li>
            <li>预检请求会在请求头里带上请求的源、正式请求的method、正式请求会携带的请求头等信息</li>
            <li>预检请求没有body</li>
          </ul>
        </v-carousel-item>
        <v-carousel-item>
          <h3>预检响应</h3>
          <p>
            <pre>
              HTTP/1.1 204 No Content
              Access-Control-Allow-Origin: http://www.example.com
              Access-Control-Allow-Methods: POST
              Access-Control-Allow-Headers: Content-Type, Authorization
              Access-Control-Expose-Headers: Content-Type
              Access-Control-Allow-Credentials: true
            </pre>
          </p>
          <hr/>
          <ul>
            <li>预检响应的状态码为204（成功时）或400（失败时）</li>
            <li>预检响应的响应头里应携带允许访问的源、允许的请求method、允许携带的请求头、以及是否允许携带cookie等信息</li>
            <li>预检响应没有body</li>
          </ul>
        </v-carousel-item>
        <v-carousel-item>
          <h3>正式请求</h3>
          <p>
            <pre>
              POST http://api.example.com/foo/bar HTTP/1.1
              Origin: http://www.example.com
              Content-Type: application/json; charset=utf-8
              Content-Length: 17
              Authorization: Bearer xxxxx.yyyyy.zzzzz
              Cookie: xxxxxxxxxxxxxxxxxxxxx

              {"foo":1,"bar":2}
            </pre>
          </p>
          <hr/>
          <ul>
            <li>正式请求和不跨域时没区别</li>
          </ul>
        </v-carousel-item>
        <v-carousel-item>
          <h3>正式响应</h3>
          <p>
            <pre>
              HTTP/1.1 200 OK
              Content-Type: application/json; charset=utf-8
              Content-Length: 17
              Access-Control-Allow-Origin: http://www.example.com
              Access-Control-Allow-Credentials: true
              Access-Control-Expose-Headers: Content-Type

              {"foo":1,"bar":2}
            </pre>
          </p>
          <hr/>
          <ul>
            <li>正式响应依然要带上允许访问的源和是否允许cookie</li>
            <li>正式响应还要带上哪些头允许JS访问</li>
          </ul>
        </v-carousel-item>
      </v-carousel>
    </section>
    <section>
      <h2>CORS的实现</h2>
      <p>
        从实现角度来看，CORS只是一组请求/响应头而已，而且请求头浏览器自己会加，开发者只需添加响应头就行了。
      </p>
      <p>
        我们可以给每个URL、每个method单独加CORS支持。例如Spring MVC可以给每个controller的每个action添加CORS：<br/>
        <pre>
          @Controller
          public class MyController {

            <span class="error--text">@CrossOrigin({"http://foo.com", "http://bar.com"})</span>
            @PostMapping("/")
            public String index() {
              return "bar";
            }
          }
        </pre>
      </p>
      <p>
        我们也可以在web应用里全局地加CORS支持，例如Spring MVC里，你可以这样做：<br/>
        <pre>
          @Bean
          public WebMvcConfigurer corsConfigurer() {
            return new WebMvcConfigurerAdapter() {
              @Override
              public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/").allowedOrigins("http://localhost:9000");
              }
            };
          }
        </pre>
      </p>
      <p>
        我们甚至可以在反向代理层添加CORS，例如，在NGINX里，你可以这样配置：<br/>
        <pre>
          location / {
            add_header 'Access-Control-Allow-Origin' 'http://foo.com, http://bar.com' always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;

            if ($REQUEST_METHOD = OPTIONS) {
              return 204;
            }

            proxy_pass http://localhost:8080;
          }
        </pre>
      </p>
    </section>
  </div>
</template>
